package clientcore

import (
	"context"
	"errors"
	"fmt"
	"net"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"time"

	"go.opencensus.io/trace"
)

func (slf *Core) readFristRequest(ctx context.Context, conn net.Conn) {
	ctx, span := trace.StartSpan(ctx, "client.readFristRequest")
	defer span.End()

	buffer := []byte{}
	data := make([]byte, 4096)
	for {
		conn.SetReadDeadline(time.Now().Add(time.Second * 5))
		n, err := conn.Read(data)
		if err != nil {
			span.AddAttributes(trace.StringAttribute("result", "read error: "+err.Error()))
			conn.Close()
			slf.logger.Printf("readFristRequest error: %+v\n", err)
			return
		}
		//log.Printf("%s", string(data[0:n]))
		buffer = append(buffer, data[0:n]...)
		if len(buffer) > 4096 {
			span.AddAttributes(trace.StringAttribute("result", "message too large"))
			conn.Write([]byte("HTTP/1.1 400 Bad Request\r\n\r\n"))
			conn.Close()
			return
		}
		//sock5
		if len(buffer) != 0 && buffer[0] == 5 {
			conn.Write([]byte{0x05, 0x00})
			err = slf.socks5Proxy(ctx, conn)
			if err != nil {
				conn.Close()
			}
			slf.logger.Printf("readFristRequest socks5: %+v", err)
			span.AddAttributes(trace.StringAttribute("result", fmt.Sprintf("sock5: %v", err)))
			return
		}

		idx := strings.Index(string(buffer), "\r\n\r\n")
		if idx == -1 {
			continue
		}

		vstr := strings.Split(string(buffer[0:idx]), " ")
		if len(vstr) < 3 {
			span.AddAttributes(trace.StringAttribute("result", "frist line error: "+string(buffer[0:idx])))
			conn.Write([]byte("HTTP/1.1 400 Bad Request\r\n\r\n"))
			conn.Close()
			return
		}
		addr := ""
		tunnel := vstr[0] == http.MethodConnect
		if tunnel {
			if idx != len(buffer)-len("\r\n\r\n") {
				span.AddAttributes(trace.StringAttribute("result", `not endwith \r\n\r\n`))
				conn.Write([]byte("HTTP/1.1 400 Bad Request\r\n\r\n"))
				conn.Close()
				return
			}
			addr = vstr[1]
		} else {
			uri, err := url.Parse(vstr[1])
			if err != nil {
				span.AddAttributes(trace.StringAttribute("result", "url.Parse error: "+err.Error()))
				conn.Write([]byte("HTTP/1.1 400 Bad Request\r\n\r\n"))
				conn.Close()
			}
			addr = uri.Host
			if uri.Port() == "" {
				switch strings.ToLower(uri.Scheme) {
				case "http", "ws":
					addr += ":80"
				case "https", "wss":
					addr += ":443"
				default:
					span.AddAttributes(trace.StringAttribute("result", "not support protol: "+vstr[1]))
					conn.Write([]byte("HTTP/1.1 400 Bad Request\r\n\r\n"))
					conn.Close()
					return
				}
			}

		}

		//
		wchan, err := slf.connectUpstream(ctx, conn, addr)
		if err != nil {
			conn.Write([]byte("HTTP/1.1 503 Service Unavailable\r\n\r\n"))
			conn.Close()
			return
		}
		if tunnel {
			conn.Write([]byte("HTTP/1.1 200 Connection Established\r\n\r\n"))
		} else {
			wchan <- append([]byte{'1'}, buffer...)
		}
		span.AddAttributes(trace.StringAttribute("result", "ok"))
		return
	}
}

func (slf *Core) socks5Proxy(ctx context.Context, conn net.Conn) error {
	var b [1024]byte
	n, err := conn.Read(b[:])
	if err != nil {
		return err
	}
	if n < 6 {
		return errors.New("message too small")
	}
	/*
		+----+-----+-------+------+----------+----------+
		|VER | CMD |  RSV  | ATYP | DST.ADDR | DST.PORT |
		+----+-----+-------+------+----------+----------+
		| 1  |  1  | X'00' |  1   | Variable |    2     |
		+----+-----+-------+------+----------+----------+
	*/
	var host, port string
	switch b[3] {
	case 0x01: //IP V4
		if n != 10 {
			return fmt.Errorf("ipv4 message length err, expect 10 but %d", n)
		}
		host = net.IPv4(b[4], b[5], b[6], b[7]).String()
	case 0x03: //域名
		host = string(b[5 : n-2]) //b[4]表示域名的长度
	case 0x04: //IP V6
		if n != 22 {
			return fmt.Errorf("ipv6 message length err, expect 22 but %d", n)
		}
		host = net.IP{b[4], b[5], b[6], b[7], b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15], b[16], b[17], b[18], b[19]}.String()
	}
	port = strconv.Itoa(int(b[n-2])<<8 | int(b[n-1]))

	addr := host + ":" + port
	_, err = slf.connectUpstream(ctx, conn, addr)
	if err != nil {
		conn.Write([]byte{0x05, 0x05, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
		return err
	}
	conn.Write([]byte{0x05, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
	return nil
}
